﻿
using System.Drawing;
using ZXing.Common;
using ZXing.PDF417.Internal;
using ZXing.Rendering;
using ZXing;
using static System.Net.Mime.MediaTypeNames;
using System.Drawing.Imaging;
using ZXing.QrCode.Internal;
using ZXing.QrCode;
using Microsoft.Extensions.Options;
using System.Runtime.InteropServices;
using ZXing.OneD;

namespace Simple.Common.Helpers
{
    public class QrCodeHelper
    {
        #region C#生成带有Log的二维码
        /// <summary>
        /// 生成带Logo的二维码
        /// </summary>
        /// <param name="text">文本内容</param>
        public static void GenerateQRCode(string content, int outWidth, int outHeight, ImageFormat imgFrt, out string outQRCodeImageBase64String, System.Drawing.Image? logo = null)
        {
            outQRCodeImageBase64String = string.Empty;

            //构造二维码写码器
            //MultiFormatWriter writer = new MultiFormatWriter();
            //Dictionary<EncodeHintType, object> hint = new Dictionary<EncodeHintType, object>();
            //hint.Add(EncodeHintType.CHARACTER_SET, "UTF-8");
            //hint.Add(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);

            //生成二维码 
           
            var write = new ZXing.BarcodeWriter<Bitmap>
            {
                Format = BarcodeFormat.QR_CODE,
                Renderer = (IBarcodeRenderer<Bitmap>)Activator.CreateInstance(typeof(BitmapRenderer)),
                Options = new QrCodeEncodingOptions() //EncodingOptions的派生类
                {
                    CharacterSet = "UTF-8",//二维码使用中文需要设置的编码格式
                    Height = outHeight,
                    Width = outWidth,
                    Margin = 0
                }
            };

            var renderer = write.Renderer as BitmapRenderer;
            renderer.Foreground = Color.FromArgb(0x43A37D); //设置了前景颜色。
            var bitMatirx = write.Encode(content);
            Bitmap map = write.Write(bitMatirx);
            Bitmap outBitMap = null;
            if (logo != null)
            {
                //获取二维码实际尺寸（去掉二维码两边空白后的实际尺寸）
                int[] rectangle = bitMatirx.getEnclosingRectangle();

                //计算插入图片的大小和位置
                int middleW = Math.Min((int)(rectangle[2] / 3.5), logo.Width);
                int middleH = Math.Min((int)(rectangle[3] / 3.5), logo.Height);
                int middleL = (map.Width - middleW) / 2;
                int middleT = (map.Height - middleH) / 2;

                outBitMap = new Bitmap(map.Width, map.Height, PixelFormat.Format32bppArgb);
                using (Graphics g = Graphics.FromImage(outBitMap))
                {
                    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                    g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                    g.DrawImage(map, 0, 0);
                }

                //将二维码插入图片
                Graphics myGraphic = Graphics.FromImage(outBitMap);

                //白底
                myGraphic.FillRectangle(Brushes.White, middleL, middleT, middleW, middleH);
                myGraphic.DrawImage(logo, middleL, middleT, middleW, middleH);
            }

            using (var memory = new MemoryStream())
            {
                //保存成图片
                if (outBitMap != null)
                {
                    outBitMap.Save(memory, imgFrt);
                }
                else
                {
                    map.Save(memory, imgFrt);
                }

                var bytes = memory.GetBuffer();
                outQRCodeImageBase64String = $"data:image/png;base64,{Convert.ToBase64String(bytes)}";
            }

            if (outBitMap != null)
            {
                outBitMap.Dispose();
            }

            map.Dispose();
        }
        #endregion
    }

    public class BitmapRenderer : IBarcodeRenderer<Bitmap>
    {
        /// <summary>
        /// Gets or sets the foreground color.
        /// </summary>
        /// <value>The foreground color.</value>
        public Color Foreground { get; set; }

        /// <summary>
        /// Gets or sets the background color.
        /// </summary>
        /// <value>The background color.</value>
        public Color Background { get; set; }

        /// <summary>
        /// Gets or sets the resolution which should be used to create the bitmap
        /// If nothing is set the current system settings are used
        /// </summary>
        public float? DpiX { get; set; }

        /// <summary>
        /// Gets or sets the resolution which should be used to create the bitmap
        /// If nothing is set the current system settings are used
        /// </summary>
        public float? DpiY { get; set; }

        /// <summary>
        /// Gets or sets the text font.
        /// </summary>
        /// <value>
        /// The text font.
        /// </value>
        public Font TextFont { get; set; }

        private static readonly Font DefaultTextFont;

        static BitmapRenderer()
        {
            try
            {
                DefaultTextFont = new Font("Arial", 10, FontStyle.Regular);
            }
            catch (Exception exc)
            {
                // have to ignore, no better idea
                System.Diagnostics.Trace.TraceError("default text font (Arial, 10, regular) couldn't be loaded: {0}", exc.Message);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BitmapRenderer"/> class.
        /// </summary>
        public BitmapRenderer()
        {
            Foreground = Color.Black;
            Background = Color.White;
            TextFont = DefaultTextFont;
        }

        /// <summary>
        /// Renders the specified matrix.
        /// </summary>
        /// <param name="matrix">The matrix.</param>
        /// <param name="format">The format.</param>
        /// <param name="content">The content.</param>
        /// <returns></returns>
        public Bitmap Render(BitMatrix matrix, BarcodeFormat format, string content)
        {
            return Render(matrix, format, content, null);
        }

        /// <summary>
        /// Renders the specified matrix.
        /// </summary>
        /// <param name="matrix">The matrix.</param>
        /// <param name="format">The format.</param>
        /// <param name="content">The content.</param>
        /// <param name="options">The options.</param>
        /// <returns></returns>
        virtual public Bitmap Render(BitMatrix matrix, BarcodeFormat format, string content, EncodingOptions options)
        {
            var width = matrix.Width;
            var height = matrix.Height;
            var font = TextFont ?? DefaultTextFont;
            var emptyArea = 0;
            var outputContent = font != null &&
                                (options == null || !options.PureBarcode) &&
                                !String.IsNullOrEmpty(content) &&
                                (format == BarcodeFormat.CODE_39 ||
                                 format == BarcodeFormat.CODE_93 ||
                                 format == BarcodeFormat.CODE_128 ||
                                 format == BarcodeFormat.EAN_13 ||
                                 format == BarcodeFormat.EAN_8 ||
                                 format == BarcodeFormat.CODABAR ||
                                 format == BarcodeFormat.ITF ||
                                 format == BarcodeFormat.UPC_A ||
                                 format == BarcodeFormat.UPC_E ||
                                 format == BarcodeFormat.MSI ||
                                 format == BarcodeFormat.PLESSEY);

            if (options != null)
            {
                if (options.Width > width)
                {
                    width = options.Width;
                }
                if (options.Height > height)
                {
                    height = options.Height;
                }
            }

            // calculating the scaling factor
            var pixelsizeWidth = width / matrix.Width;
            var pixelsizeHeight = height / matrix.Height;

            if (pixelsizeWidth != pixelsizeHeight)
            {
                if (format == BarcodeFormat.QR_CODE ||
                    format == BarcodeFormat.AZTEC ||
                    format == BarcodeFormat.DATA_MATRIX ||
                    format == BarcodeFormat.MAXICODE ||
                    format == BarcodeFormat.PDF_417)
                {
                    // symetric scaling
                    pixelsizeHeight = pixelsizeWidth = pixelsizeHeight < pixelsizeWidth ? pixelsizeHeight : pixelsizeWidth;
                }
            }

            // create the bitmap and lock the bits because we need the stride
            // which is the width of the image and possible padding bytes
            var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
            var dpiX = DpiX ?? DpiY;
            var dpiY = DpiY ?? DpiX;
            if (dpiX != null)
                bmp.SetResolution(dpiX.Value, dpiY.Value);

            using (var g = Graphics.FromImage(bmp))
            {
                var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
                try
                {
                    var pixels = new byte[bmpData.Stride * height];
                    var padding = bmpData.Stride - (3 * width);
                    var index = 0;
                    var color = Background;

                    // going through the lines of the matrix
                    for (int y = 0; y < matrix.Height; y++)
                    {
                        // stretching the line by the scaling factor
                        for (var pixelsizeHeightProcessed = 0; pixelsizeHeightProcessed < pixelsizeHeight; pixelsizeHeightProcessed++)
                        {
                            // going through the columns of the current line
                            for (var x = 0; x < matrix.Width; x++)
                            {
                                color = matrix[x, y] ? Foreground : Background;
                                // stretching the columns by the scaling factor
                                for (var pixelsizeWidthProcessed = 0; pixelsizeWidthProcessed < pixelsizeWidth; pixelsizeWidthProcessed++)
                                {
                                    pixels[index++] = color.B;
                                    pixels[index++] = color.G;
                                    pixels[index++] = color.R;
                                }
                            }
                            // fill up to the right if the barcode doesn't fully fit in 
                            for (var x = pixelsizeWidth * matrix.Width; x < width; x++)
                            {
                                pixels[index++] = Background.B;
                                pixels[index++] = Background.G;
                                pixels[index++] = Background.R;
                            }
                            index += padding;
                        }
                    }
                    // fill up to the bottom if the barcode doesn't fully fit in 
                    for (var y = pixelsizeHeight * matrix.Height; y < height; y++)
                    {
                        for (var x = 0; x < width; x++)
                        {
                            pixels[index++] = Background.B;
                            pixels[index++] = Background.G;
                            pixels[index++] = Background.R;
                        }
                        index += padding;
                    }
                    // fill the bottom area with the background color if the content should be written below the barcode
                    if (outputContent)
                    {
                        var textAreaHeight = font.Height;

                        emptyArea = height > textAreaHeight ? textAreaHeight : 0;

                        if (emptyArea > 0)
                        {
                            index = (width * 3 + padding) * (height - emptyArea);
                            for (int y = height - emptyArea; y < height; y++)
                            {
                                for (var x = 0; x < width; x++)
                                {
                                    pixels[index++] = Background.B;
                                    pixels[index++] = Background.G;
                                    pixels[index++] = Background.R;
                                }
                                index += padding;
                            }
                        }
                    }

                    //Copy the data from the byte array into BitmapData.Scan0
                    Marshal.Copy(pixels, 0, bmpData.Scan0, pixels.Length);
                }
                finally
                {
                    //Unlock the pixels
                    bmp.UnlockBits(bmpData);
                }

                // output content text below the barcode
                if (emptyArea > 0)
                {
                    switch (format)
                    {
                        case BarcodeFormat.UPC_E:
                        case BarcodeFormat.EAN_8:
                            if (content.Length < 8)
                                content = OneDimensionalCodeWriter.CalculateChecksumDigitModulo10(content);
                            if (content.Length > 4)
                                content = content.Insert(4, "   ");
                            break;
                        case BarcodeFormat.EAN_13:
                            if (content.Length < 13)
                                content = OneDimensionalCodeWriter.CalculateChecksumDigitModulo10(content);
                            if (content.Length > 7)
                                content = content.Insert(7, "   ");
                            if (content.Length > 1)
                                content = content.Insert(1, "   ");
                            break;
                        case BarcodeFormat.UPC_A:
                            if (content.Length < 12)
                                content = OneDimensionalCodeWriter.CalculateChecksumDigitModulo10(content);
                            if (content.Length > 11)
                                content = content.Insert(11, "   ");
                            if (content.Length > 6)
                                content = content.Insert(6, "   ");
                            if (content.Length > 1)
                                content = content.Insert(1, "   ");
                            break;
                    }
                    var brush = new SolidBrush(Foreground);
                    var drawFormat = new StringFormat { Alignment = StringAlignment.Center };
                    g.DrawString(content, font, brush, pixelsizeWidth * matrix.Width / 2, height - emptyArea, drawFormat);
                }
            }

            return bmp;
        }
    }
}
